package tetris;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import javax.swing.*;
import tetris.Form.Piece;

/**
 * Creates game engine.
 * @author dejvid
 *
 */
public class GamePanel extends JPanel implements ActionListener {
	
		private static final long serialVersionUID = 1L;
		// rozmiar planszy
		final int PanelWidth = 9;
	    final int PanelHeight = 19;
	    
	    boolean isEndOfBoard = false;
	    boolean isStarted = false;
	    boolean isPaused = false;
	    boolean isGameOver = false;
	    public static int finalScore = 0;
	    int posX = 0;
	    int posY = 0;

	    Timer timer;
	    Form thisPiece;
	    Piece[] board;
	    
	    private JLabel scoreBoard;
	    private JTextField gamePauseText;
	    
	    /**
	     * Creates game engine. Adds Timer class, and scoreBoard.
	     */
	    public GamePanel(StartGame p) {
	       setFocusable(true);
	       setLayout(null);
	       thisPiece = new Form();
	       timer = new Timer(400, this);
	       timer.start(); 
	       board = new Piece[ PanelWidth * PanelHeight ];  
	       scoreBoard = p.getStatusBar();
	       scoreBoard.setForeground(Color.WHITE);   
	       addKeyListener( new KeySystem() );
	       clearBoard();
	    }

	    /**
	     * Checks if piece fallen at the bottom of board.
	     * If yes, loads method which generates next piece.
	     */
	    public void actionPerformed(ActionEvent e) {
	        if (isEndOfBoard) {
	            isEndOfBoard = false;
	            getNextPiece();
	        } else {
	            moveDown();
	        }
	    }
	    
	    int squareWidth() {
	    	return (int) getSize().getWidth() / PanelWidth; 
	    }
	    
	    int squareHeight() { 
	    	return (int) getSize().getHeight() / PanelHeight; 
	    }
	    
	    Piece spawnAt( int x, int y ) { 
	    	return board[(y * PanelWidth) + x]; 
	    }

	    /**
	     * Runns game.
	     */
	    public void start()
	    {
	        if (isPaused)
	            return;

	        isStarted = true;
	        isEndOfBoard = false;
	        finalScore = 0;
	        clearBoard();
	        getNextPiece();
	        timer.start();
	    }

	    /**
	     * Pauses game after hitting ESC key.
	     * Adds textField: GAME PAUSED.
	     */
	    public void pause() {	
	        if (!isStarted)
	            return;

	        isPaused = !isPaused;
	        if (isPaused) {
	        	timer.stop();
	    		gamePauseText = new JTextField();
	        	gamePauseText.setBounds(0, 5, 300, 74);
	        	gamePauseText.setEditable(false);
	        	gamePauseText.setFont(new Font("OCR A Std", Font.PLAIN, 38));
	        	gamePauseText.setBackground(Color.DARK_GRAY);
	        	gamePauseText.setForeground(Color.WHITE);
	        	gamePauseText.setText("GAME PAUSED");
	        	gamePauseText.setColumns(10);
	        	gamePauseText.setBorder(null);
	        	gamePauseText.setFocusable(false);
	        	gamePauseText.setHorizontalAlignment(JTextField.CENTER);
	        	gamePauseText.setDoubleBuffered(true);
	        	add(gamePauseText);
	        	
	        } else {
	            timer.start();   
	            gamePauseText.setVisible(false);
	        }
	        repaint();
	    }
	    
	    /**
	     * Makes Game Over Menu visible. 
	     */
	    public void printGameOverMenu() {
			add( new GameOverMenu() );
	    }
    	
	    /**
	     * Draws board and tetris pieces.
	     */
	    public void paint(Graphics g) { 
	        super.paint(g);

	        Dimension size = getSize();
	        int boardTop = (int) size.getHeight() - PanelHeight * squareHeight();


	        for (int i = 0; i < PanelHeight; ++i) {
	            for (int j = 0; j < PanelWidth; ++j) {
	                Piece shape = spawnAt(j, PanelHeight - i - 1);
	                if (shape != Piece.None)
	                    drawSquare(g, 0 + j * squareWidth(),
	                               boardTop + i * squareHeight(), shape);
	            }
	        }

	        if (thisPiece.getShape() != Piece.None) {
	            for (int i = 0; i < 4; ++i) {
	                int x = posX + thisPiece.x(i);
	                int y = posY - thisPiece.y(i);
	                drawSquare(g, 0 + x * squareWidth(),
	                           boardTop + (PanelHeight - y - 1) * squareHeight(),
	                           thisPiece.getShape());
	            }
	        }
	    }

	    /**
	     * Moves piece one line down.
	     */
	    private void moveDown() {
	        if (!move(thisPiece, posX, posY - 1))
	            pieceDropped();
	    }

	    /**
	     * Clears board. Fill with None Shape.
	     */
	    private void clearBoard() {
	        for (int i = 0; i < PanelHeight * PanelWidth; ++i)
	            board[i] = Piece.None;
	    }
	    
	    /**
	     * Deletes piece from the bottom of board.
	     * If piece fallen at the bottom calls method which generates next piece.
	     */
	    private void pieceDropped()
	    {
	        for (int i = 0; i < 4; ++i) {
	            int x = posX + thisPiece.x(i);
	            int y = posY - thisPiece.y(i);
	            board[(y * PanelWidth) + x] = thisPiece.getShape();
	        }

	        deleteFilledLines();

	        if (!isEndOfBoard)
	            getNextPiece();
	    }

	    /**
	     * Generates next piece under condition that current piece has fallen at the bottom of board.
	     * Condition checks whether can we move our piece and returns information if game is over.
	     * If yes, it stops timer and calls methods to save score and print Game Over Menu.
	     */
	    private void getNextPiece() {
	        thisPiece.generateRandomPiece();
	        posX = PanelWidth / 2;
	        posY = PanelHeight - 1 + thisPiece.minY();

	        if ( !move( thisPiece, posX, posY ) ) {
	        	isGameOver = true;
	        	isStarted = false;
	        	thisPiece.generateShape(Piece.None);
	            timer.stop();
	            saveScore();
	            clearBoard();
	            moveDown();
	            printGameOverMenu();     
	        }
	    }
	    
	    /**
	     * Saves score after game finish.
	     */
	    public void saveScore() {
	    	if( isGameOver ) {
	    		ScoresSystem sm = new ScoresSystem();
	    		sm.addScore(InsertName.nickname, finalScore);
	    		}
	    }

	    /**
	     * Checks if it's possible to move a piece.
	     * @return false if piece is at the top of board or true if not.
	     */
	    private boolean move( Form getNextPiece, int newX, int newY ) {
	        for (int i = 0; i < 4; ++i) {
	            int x = newX + getNextPiece.x(i);
	            int y = newY - getNextPiece.y(i);
	            if ( x < 0 || x >= PanelWidth || y < 0 || y >= PanelHeight )
	                return false;
	            if (spawnAt( x, y ) != Piece.None )
	                return false;
	        }

	        thisPiece = getNextPiece;
	        posX = newX;
	        posY = newY;
	        repaint();
	        return true;
	    }

	    /**
	     * Deletes full lines, and adds appropriate score points.
	     */
	    private void deleteFilledLines() {
	        int numberFilledLines = 0;

	        for (int i = PanelHeight - 1; i >= 0; --i) {
	            boolean lineIsFull = true;

	            for (int j = 0; j < PanelWidth; ++j) {
	                if (spawnAt(j, i) == Piece.None) {
	                    lineIsFull = false;
	                    break;
	                }
	            }

	            if (lineIsFull) {
	                ++numberFilledLines;
	                for (int k = i; k < PanelHeight - 1; ++k) {
	                    for (int j = 0; j < PanelWidth; ++j)
	                         board[(k * PanelWidth) + j] = spawnAt(j, k + 1);
	                }
	            }
	        }

	        if (numberFilledLines > 0) {
	        	if( numberFilledLines == 1 ) {
	        		finalScore++;
	        	}
	        	if( numberFilledLines == 2 ) {
	        		finalScore += 4;
	        	}
	        	if( numberFilledLines == 3 ) {
	        		finalScore += 9;
	        	}
	        	if( numberFilledLines == 4 ) {
	        		finalScore += 16;
	        	}
	      
	            scoreBoard.setText(" SCORE: " + String.valueOf(finalScore));
	            isEndOfBoard = true;
	            thisPiece.generateShape(Piece.None);
	            repaint();
	        }
	     }

	    /**
	     * Draws on screen tetris pieces.
	     * @param g 2dgraphic.
	     * @param x	position x.
	     * @param y	position y.
	     * @param shape gets information about piece shape.
	     */
	    private void drawSquare( Graphics g, int x, int y, Piece shape )
	    {
	    	Color colors[] = { 
	    			new Color(0, 0, 0), 		// None
	    			new Color(204, 102, 102), 	// PieceZ
	                new Color(102, 204, 102),	// PieceS
	                new Color(102, 102, 204), 	// PieceLine
	                new Color(204, 204, 102), 	// PieceT
	                new Color(204, 102, 204), 	// PieceSquare
	                new Color(102, 204, 204),	// PieceL
	                new Color(218, 170, 0)		// PieceMirrored
	        };


	        Color color = colors[ shape.ordinal() ];
	        g.setColor( color );
	        
	        g.fillRect( x + 1, y + 1, squareWidth() - 2, squareHeight() - 2 );
	        
	        g.setColor( color.brighter() );
	        g.drawLine( x, y + squareHeight() - 1, x, y );
	        g.drawLine( x, y, x + squareWidth() - 1, y );

	        g.setColor( color.darker() );
	        g.drawLine( x + 1, y + squareHeight() - 1, x + squareWidth() - 1, y + squareHeight() - 1 );
	        g.drawLine( x + squareWidth() - 1, y + squareHeight() - 1, x + squareWidth() - 1, y + 1 );
	      
	    }

	    /**
	     * Implements keyboard control.
	     * @author dejvid
	     *
	     */
	    class KeySystem extends KeyAdapter {
	  
	         public void keyPressed(KeyEvent e) { 
	             
	             int keycode = e.getKeyCode();
	             if(keycode == KeyEvent.VK_ESCAPE) {
	                 pause();
	                 return;
	             }

	             if(isPaused)
	                 return;
	             
	             switch (keycode) {
	             case KeyEvent.VK_LEFT:
	                 move(thisPiece, posX - 1, posY);
	                 break;
	             case KeyEvent.VK_RIGHT:
	                 move(thisPiece, posX + 1, posY);
	                 break;
	             case KeyEvent.VK_UP:
	                 move(thisPiece.pieceRotate(), posX, posY);
	                 break;
	             case KeyEvent.VK_DOWN:
	            	 moveDown();
	            	 break;
	             }
	         }
	    } 	    
}
